package cn.edu.neu.vmmigeval.cloud.policy.impl;

import cn.edu.neu.vmmigeval.cloud.config.Migration;
import cn.edu.neu.vmmigeval.cloud.entity.Host;
import cn.edu.neu.vmmigeval.cloud.entity.Vm;
import cn.edu.neu.vmmigeval.cloud.policy.MigratedHostPolicy;
import cn.edu.neu.vmmigeval.cloud.policy.VmPlacementPolicy;
import cn.edu.neu.vmmigeval.evaluation.PowerModel;

import java.util.*;

public class VmPlacementPolicyPABFD extends VmPlacementPolicy {
	
	public VmPlacementPolicyPABFD(List<Host> hostList, MigratedHostPolicy migratedHostPolicy){
		super(hostList,migratedHostPolicy);
	}
	
	public VmPlacementPolicyPABFD(MigratedHostPolicy migratedHostPolicy){
		super(migratedHostPolicy);
	}

	
	public VmPlacementPolicyPABFD(){
		
	}
	public List<Migration> getNewVmPlacement(List<Vm> vmsToMigrate, List<Host> hosts) {
		List<Migration> migrationMap = new LinkedList<Migration>();
		//sortVmByDecreasingUtilization(vmsToMigrate);
		List<Host> hs = getExcludedHosts(hosts);
		
		for (Vm vm : vmsToMigrate) {
			Host targetHost = findHostForVm(vm, hs);
			if (targetHost != null) {
				//allocatedHost.vmCreate(vm);
				//Log.printLine("Broker #" + vm.getId() + " allocated to host #" + allocatedHost.getId());

				/*Migration migrate = new Migration();
				vm.setInMigration(true);
				migrate.setSrcHost(vm.getHost());
				migrate.setDestHost(allocatedHost);
				List<Vm>  vms = new LinkedList<Vm>();
				vms.add(vm);
				migrate.setVms(vms);
				migrationMap.add(migrate);*/
				
				
				Host srcHost = vm.getHost();
				vm.setInMigration(true);
				List<Vm> vms = new ArrayList<Vm>();
				vms.add(vm);
				Migration migration = new Migration();
				migration.setSrcHost(srcHost);
				migration.setDestHost(targetHost);
				migration.setVms(vms);
				migration.setClocks((int)((vm.getCurrentRam()/(targetHost.getTotalBw()-targetHost.getCurrentBw()+10)))+1);
			//	System.out.println("clocks is :"+migration.getClocks());
				migration.setEnd(false);
				
				srcHost.setMigration(true);
				targetHost.setMigration(true);
				targetHost.setActive(0);
				migrationMap.add(migration);
			}
		}
		return migrationMap;
	}
	
	/**
     * @author zzh
     * @param vmList
     */
    private void sortVmByDecreasingUtilization(List<Vm> vmList) {
        Collections.sort(vmList, new Comparator<Vm>() {
            @Override
            public int compare(Vm a, Vm b) throws ClassCastException {
                Double aUtilization =  (double)a.getCurrentCpu()/a.getTotalCpu();
                Double bUtilization = (double)b.getCurrentCpu()/b.getTotalCpu();
                return bUtilization.compareTo(aUtilization);
            }
        });
    }
    
    /**
	 * Find host for vm.
	 * 
	 * @param vm the vm
	 * @param excludedHosts the excluded hosts
	 * @return the power host
	 */
	public Host findHostForVm(Vm vm, List<Host> excludedHosts) {
		double minPower = Double.MAX_VALUE;
		Host allocatedHost = null;
		//PowerModel pm = null;
		for (Host host : excludedHosts) {
			if (host.isSuitableForVm(vm)) {
				if (getUtilizationOfCpuMips(host) != 0 && isHostOverUtilizedAfterAllocation(host, vm)) {
					continue;
				}

				try {
					double powerAfterAllocation = getPowerAfterAllocation(host, vm);
					if (powerAfterAllocation != -1) {
						
						double powerDiff = powerAfterAllocation - PowerModel.getPowerOfHost(0, host); //host.getPower();
						if (powerDiff < minPower) {
							minPower = powerDiff;
							allocatedHost = host;
						}
					}
				} catch (Exception e) {
				}
			}
		}
		return allocatedHost;
	}
	
	/**
	 * Checks if is host over utilized after allocation.
	 * 
	 * @param host the host
	 * @param vm the vm
	 * @return true, if is host over utilized after allocation
	 */
	/*protected boolean isHostOverUtilizedAfterAllocation(Host host, Vm vm) {
		boolean isHostOverUtilizedAfterAllocation = true;
		Host host2 = vm.getHost();
		if (host.vmCreate(vm)) {
			isHostOverUtilizedAfterAllocation = this.getMigratedHostPolicy().isHostOverUtilized(host);
			host.vmDestroy(vm);
			vm.setHost(host2);
		}
		return isHostOverUtilizedAfterAllocation;
	}*/
	
	/**
	 * Gets the utilization of the CPU in MIPS for the current potentially allocated VMs.
	 *
	 * @param host the host
	 *
	 * @return the utilization of the CPU in MIPS
	 */
	protected double getUtilizationOfCpuMips(Host host) {
		double hostUtilizationMips = 0;
		for (Vm vm2 : host.getVmList()) {
			if (host.getVmsMigratingIn().contains(vm2)) {
				// calculate additional potential CPU usage of a migrating in Broker
			//	hostUtilizationMips += host.getTotalAllocatedMipsForVm(vm2) * 0.9 / 0.1;
				hostUtilizationMips += vm2.getTotalCpu() * 0.9 / 0.1;
			}
		//	hostUtilizationMips += host.getTotalAllocatedMipsForVm(vm2);
			hostUtilizationMips += vm2.getTotalCpu();
		}
		return hostUtilizationMips;
	}
	
	public double getPowerAfterAllocation(Host host,Vm vm){
		
		
		double cpu = (double)(host.getCurrentCpu()+vm.getCurrentCpu())/host.getTotalCpu();
		double ram = (double)(host.getCurrentRam()+vm.getCurrentRam())/host.getTotalRam();
		double bw = (double)(host.getCurrentBw()+vm.getCurrentBw())/host.getTotalBw();
		double disk = (double)(host.getCurrentDisk()+vm.getCurrentDisk())/host.getTotalDisk();
		
		return PowerModel.getPowerOfHostByCpuOR(cpu, ram, bw, disk);
		
		
	}
	
	
}
